package tp.badugi.server;

import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;

import tp.badugi.net.ActionRequestResponse;
import tp.badugi.net.GameStatus;
import tp.badugi.net.ServerNetworkingChannel;
import tp.badugi.net.iTableRequestListener;
import tp.badugi.net.exceptions.RequestTimedOutException;

public class Player implements iTableRequestListener {

	private ServerNetworkingChannel netChannel;
	private Table table;
	private String playerName;
	private List<Card> cards;
	private long chipCount;
	private long potContribution;
	private boolean inPlay;
	private boolean connected;
	
	public Player(
			Table table, 
			ServerNetworkingChannel netChannel,
			String playerName, 
			long chipCount
	) {
		this.table = table;
		this.netChannel = netChannel;
		this.playerName = playerName;
		this.chipCount = chipCount;
		cards = new ArrayList<Card>();
		connected = true;
		netChannel.setTableListener(this);
	}
	
	public void awardChips(long amountWon) {
		chipCount += amountWon;
	}
	
	public void discardCards() {
		cards.clear();
	}
	
	public List<Card> getCards() {
		return cards;
	}
	
	public long getChipCount() {
		return chipCount;
	}
	
	@Override
	public GameStatus getGameStatus() {
		GameStatus gameStatus = table.getGameStatus();
		GameStatus filledInGameStatus = new GameStatus(gameStatus);
		return filledInGameStatus;
	}
	
	public String getName() {
		return playerName;
	}
	
	public long getPotContribution() {
		return potContribution;
	}
	
	public boolean isConnected() {
		return connected;
	}

	public boolean isInPlay() {
		return inPlay;
	}

	@Override
	public void leaveTable() {
		connected = false;
		table.removePlayer(this);
	}

	public void notifyBettingRoundChanged(int bettingRound, GameStatus gameStatus) {
		if (connected) {
			GameStatus filledInGameStatus = new GameStatus(gameStatus);
			fillInGameStatus(filledInGameStatus);
			netChannel.notifyBettingRoundChanged(bettingRound, filledInGameStatus);
		}
	}

	public void notifyGameAboutToRestart(int timeUntilRestart) {
		if (connected) {
			netChannel.notifyGameAboutToRestart(timeUntilRestart);
		}
	}

	public void notifyGameAboutToStart(int timeUntilStart, GameStatus gameStatus) {
		if (connected) {
			GameStatus filledInGameStatus = new GameStatus(gameStatus);
			fillInGameStatus(filledInGameStatus);
			netChannel.notifyGameAboutToStart(timeUntilStart, filledInGameStatus);
		}
	}

	public void notifyPlayerDisconnected(String playerName, GameStatus gameStatus) {
		if (connected) {
			GameStatus filledInGameStatus = new GameStatus(gameStatus);
			fillInGameStatus(filledInGameStatus);
			netChannel.notifyPlayerDisconnected(playerName, filledInGameStatus);
		}
	}

	public void notifyPlayerInActionChanged(int playerIndex, String[] playerNames, GameStatus gameStatus) {
		if (connected) {
			GameStatus filledInGameStatus = new GameStatus(gameStatus);
			fillInGameStatus(filledInGameStatus);
			netChannel.notifyPlayerInActionChanged(playerIndex, playerNames, filledInGameStatus);
		}
	}
	
	public void notifyPlayerJoined(int playerIndex, GameStatus gameStatus) {
		if (connected) {
			GameStatus filledInGameStatus = new GameStatus(gameStatus);
			fillInGameStatus(filledInGameStatus);
			netChannel.notifyPlayerJoined(playerIndex, filledInGameStatus);
		}
	}

	public void notifyPlayerLeft(String playerName, GameStatus gameStatus) {
		if (connected) {
			GameStatus filledInGameStatus = new GameStatus(gameStatus);
			fillInGameStatus(filledInGameStatus);
			netChannel.notifyPlayerLeft(playerName, filledInGameStatus);
		}
	}

	public void notifyPlayerPerformedAction(
			int playerIndex, 
			String action, 
			long amount, 
			GameStatus gameStatus) 
	{
		if (connected) {
			GameStatus filledInGameStatus = new GameStatus(gameStatus);
			fillInGameStatus(filledInGameStatus);
			netChannel.notifyPlayerPerformedAction(playerIndex, action, amount, filledInGameStatus);
		}
	}
	
	public void notifyPlayerReconnected(String playerName, GameStatus gameStatus) {
		if (connected) {
			GameStatus filledInGameStatus = new GameStatus(gameStatus);
			fillInGameStatus(filledInGameStatus);
			netChannel.notifyPlayerReconnected(playerName, filledInGameStatus);
		}
	}

	public void notifyPlayerReplacedCards(String playerName, int cardCount, GameStatus gameStatus) {
		if (connected) {
			GameStatus filledInGameStatus = new GameStatus(gameStatus);
			fillInGameStatus(filledInGameStatus);
			netChannel.notifyPlayerReplacedCards(playerName, cardCount, filledInGameStatus);
		}
	}

	public void notifyPlayerWithButtonChanged(int playerIndex, String[] playerNames, GameStatus gameStatus) {
		if (connected) {
			GameStatus filledInGameStatus = new GameStatus(gameStatus);
			fillInGameStatus(filledInGameStatus);
			netChannel.notifyPlayerWithButtonChanged(playerIndex, playerNames, filledInGameStatus);
		}
	}

	public void notifyWinnerRevealed(
			int[] winnerIndexes,
			String[] playerNames,
			List<List<Card>> revealedCards,
			long[] updatedChipCounts) 
	{
		if (connected) {
			String[][] revealedCardsArray = new String[revealedCards.size()][4];
			for (int i = 0; i < revealedCards.size(); i++) {
				int j = 0;
				for (Card card : revealedCards.get(i)) {
					revealedCardsArray[i][j] = card.toString();
					j++;
				}
			}
			netChannel.notifyWinnerRevealed(
					revealedCardsArray, 
					winnerIndexes, 
					playerNames, 
					updatedChipCounts);
		}
	}

	public void placeBigBlind() {
		long bigBlind = table.getBigBlind();
		chipCount -= bigBlind;
		potContribution += bigBlind;
		table.addToPot(bigBlind);
		table.setBetAmount(bigBlind);
	}

	public void placeSmallBlind() {
		long smallBlind = table.getSmallBlind();
		chipCount -= smallBlind;
		potContribution += smallBlind;
		table.addToPot(smallBlind);
	}

	public Action requestAction(boolean raiseLimitReached, int timeout) {
		// If player disconnected then decide for him
		if (connected == false) {
			return Action.FOLD;
		}
		
		// Gather information
		long betAmount = table.getBetAmount();
		long minBet = table.getBigBlind();
		long amountToCall = betAmount - potContribution;
		
		// Determine possible actions the player can make
		EnumSet<Action> possibleActionsSet = EnumSet.of(Action.FOLD, Action.ALL_IN);
		if (betAmount == potContribution)
			possibleActionsSet.add(Action.CHECK);
		if (betAmount == potContribution && chipCount > minBet)
			possibleActionsSet.add(Action.BET);
		if (betAmount > potContribution && chipCount > amountToCall) {
			possibleActionsSet.add(Action.CALL);
			if (!raiseLimitReached && (chipCount > amountToCall + minBet))
				possibleActionsSet.add(Action.RAISE);
		}
		String[] possibleActions = new String[possibleActionsSet.size()];
		int i = 0;
		for (Action action : possibleActionsSet) {
			possibleActions[i] = action.toString();
			i++;
		}
		
		// Make request and get response
		try {
			ActionRequestResponse response = netChannel.requestAction(
					possibleActions, betAmount, minBet, timeout);
			
			// Verify response
			Action chosenAction = Action.FOLD;
			for (Action action : Action.values()) {
				if (action.toString().equals(response.getAction()) 
						&& possibleActionsSet.contains(action)) {
					chosenAction = action;
				}
			}
			chosenAction.amount = response.getAmount();
			if (chosenAction == Action.BET || chosenAction == Action.RAISE) {
				if (chosenAction.amount < minBet) {
					chosenAction.amount = minBet;
				}
				else if (chosenAction.amount + amountToCall >= chipCount) {
					chosenAction = Action.ALL_IN;
					chosenAction.amount = chipCount;
				}
			}
			
			
			// Perform verified action
			switch (chosenAction) {
			case FOLD:
				inPlay = false;
				cards.clear();
				break;
			case CHECK:
				break;
			case CALL:
				chipCount -= amountToCall;
				potContribution = betAmount;
				table.addToPot(amountToCall);
				break;
			case BET:
				chipCount -= chosenAction.amount;
				potContribution += chosenAction.amount;
				table.addToPot(chosenAction.amount);
				table.setBetAmount(potContribution);
				break;
			case RAISE:
				chipCount -= amountToCall + chosenAction.amount;
				potContribution += amountToCall + chosenAction.amount;
				table.addToPot(amountToCall + chosenAction.amount);
				table.setBetAmount(potContribution);
				break;
			case ALL_IN:
				potContribution += chipCount;
				table.addToPot(chipCount);
				table.setBetAmount(potContribution);
				chipCount = 0;
				break;
			}
			
			return chosenAction;
		}
		
		// Client did not send response on time
		catch (RequestTimedOutException e) {
			inPlay = false;
			cards.clear();
			return Action.FOLD;
		}
		
	}

	public EnumSet<Card> requestCards(int timeout) {
		// If player disconnected then decide for him
		if (connected == false) {
			return EnumSet.noneOf(Card.class);
		}
		
		// Prepare request
		String[] cardsArray = new String[cards.size()];
		int i = 0;
		for (Card card : cards) {
			cardsArray[i] = card.toString();
			i++;
		}
		
		
		// Make request and get response
		try {
			String[] response = netChannel.requestCards(cardsArray, timeout);
			
			// Verify response
			EnumSet<Card> chosenCards = EnumSet.noneOf(Card.class);
			if (response != null) {
				for (int j = 0; j < response.length; j++) {
					Card card = Card.fromString(response[j]);
					if (card != null && cards.contains(card))
						chosenCards.add(card);
				}
			}
			
			return chosenCards;
		}
		
		// Client did not send response on time
		catch (RequestTimedOutException e) {
			EnumSet<Card> chosenCards = EnumSet.noneOf(Card.class);
			return chosenCards;
		}
	}

	public void setCards(List<Card> cards) {
		GameStatus gameStatus = table.getGameStatus();
		this.cards = cards;
		inPlay = true;
		String[] cardsArray = new String[cards.size()];
		int i = 0;
		for (Card card : cards) {
			cardsArray[i] = card.toString();
			i++;
		}
		if (connected) {
			GameStatus filledInGameStatus = new GameStatus(gameStatus);
			fillInGameStatus(filledInGameStatus);
			netChannel.notifyCardsReceived(cardsArray, filledInGameStatus);
		}
	}

	public void setChips(long chips) {
		chipCount = chips;
	}

	public void setConnected(boolean connected) {
		this.connected = connected;
	}

	public void setPotContribution(long i) {
		potContribution = 0;
	}

	private void fillInGameStatus(GameStatus gameStatus) {
		int playerIndex;
		for (playerIndex = 0; playerIndex < gameStatus.playerNames.length; playerIndex++) {
			if (gameStatus.playerNames[playerIndex].equals(playerName))
				break;
		}
		gameStatus.myPlayerNumber = playerIndex;
		gameStatus.myChipCount = chipCount;
		
		String[] cardsArray = null;
		if (cards.size() != 0) {
			cardsArray = new String[cards.size()];
			int i = 0;
			for (Card card : cards) {
				cardsArray[i] = card.toString();
				i++;
			}
		}
		gameStatus.myCards = cardsArray;
	}

}
